package com.hunter.lucene.util.config;

import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockFactory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hunter.lucene.util.annotation.DocumentAnnotationConfigAdapter;
import com.hunter.lucene.util.annotation.UndocumentAnnotationConfigAdapter;
import com.hunter.lucene.util.annotation.parse.AnnotationParser;
import com.hunter.lucene.util.annotation.parse.DocumentAnnotationParser;
import com.hunter.lucene.util.annotation.parse.DocumentAnnotationWrapper;
import com.hunter.lucene.util.annotation.parse.UndocumentAnnotationParser;
import com.hunter.lucene.util.annotation.parse.UndocumentAnnotationWrapper;
import com.hunter.lucene.util.xml.parse.XmlSchemaConfigParser;
import com.hunter.schema.AnalyzerType;
import com.hunter.schema.AnalyzersType;
import com.hunter.schema.ClassType;
import com.hunter.schema.DirectoryType;
import com.hunter.schema.HunterConfig;
import com.hunter.schema.LockFactoryType;
import com.hunter.schema.MappingType;
import com.hunter.schema.RAMDirectoryType;
import com.hunter.schema.SearchBeanType;
import com.hunter.schema.SimpleFSDirectoryType;
import com.hunter.schema.WriteBeanType;

/**
 * 通过JAXB,对 xml 进行解析。
 * 
 * @author bastengao
 * 
 */
public class XmlConfigLoader implements ConfigLoader {
	private static final Logger log = LoggerFactory
			.getLogger(XmlConfigLoader.class);
	private Injector<Pair<String, org.apache.lucene.analysis.Analyzer>> analyzerInjector = new Injector<Pair<String, org.apache.lucene.analysis.Analyzer>>();
	private Injector<String> defaultAnalyzerNameInjector = new Injector<String>();
	private Injector<org.apache.lucene.store.Directory> directroyInjector = new Injector<org.apache.lucene.store.Directory>();
	private Injector<Pair<Class<?>, DocumentConfigInfo>> documentConfigInfoInjector = new Injector<Pair<Class<?>, DocumentConfigInfo>>();
	private Injector<Pair<Class<?>, UndocumentConfigInfo>> undocumentConfigInfoInjector = new Injector<Pair<Class<?>, UndocumentConfigInfo>>();

	@Override
	public Injector<Pair<String, org.apache.lucene.analysis.Analyzer>> getAnalyzerInjector() {
		return analyzerInjector;
	}

	@Override
	public Injector<String> getDefaultAnalyzerNameInjector() {
		return defaultAnalyzerNameInjector;
	}

	@Override
	public Injector<org.apache.lucene.store.Directory> getDirectoryInjector() {
		return directroyInjector;
	}

	@Override
	public Injector<Pair<Class<?>, DocumentConfigInfo>> getDocumentConfigInfoInjector() {
		return documentConfigInfoInjector;
	}

	@Override
	public Injector<Pair<Class<?>, UndocumentConfigInfo>> getUndocumentConfigInfoInjector() {
		return undocumentConfigInfoInjector;
	}

	@Override
	public void load(String configPath) throws ConfigLoadException {
		log.debug(configPath);
		Source source = new StreamSource(Thread.currentThread()
				.getContextClassLoader().getResourceAsStream(configPath));
		try {
			JAXBContext context = JAXBContext.newInstance("com.hunter.schema");
			Unmarshaller unmarshaller = context.createUnmarshaller();

			JAXBElement<HunterConfig> hunterConfigElement = unmarshaller
					.unmarshal(source, HunterConfig.class);

			HunterConfig hunterConfig = hunterConfigElement.getValue();

			try {
				AnalyzersType analyzers = hunterConfig.getAnalyzers();
				if (analyzers == null) {
					throw new ConfigLoadException("no available analyzers");
				}
				loadAnalyzers(analyzers);
			} catch (ClassNotFoundException e) {
				throw new ConfigLoadException(e);
			} catch (InstantiationException e) {
				throw new ConfigLoadException(e);
			} catch (IllegalAccessException e) {
				throw new ConfigLoadException(e);
			}

			try {
				DirectoryType directoryConfig = hunterConfig.getDirectory();
				if (directoryConfig == null) {
					throw new ConfigLoadException("no available directory");
				}
				loadDirectory(directoryConfig);
			} catch (ClassNotFoundException e) {
				throw new ConfigLoadException(e);
			} catch (InstantiationException e) {
				throw new ConfigLoadException(e);
			} catch (IllegalAccessException e) {
				throw new ConfigLoadException(e);
			} catch (IOException e) {
				throw new ConfigLoadException(e);
			}

			try {
				MappingType mapping = hunterConfig.getMapping();
				loadMappingConfig(mapping);
			} catch (SecurityException e) {
				throw new ConfigLoadException(e);
			} catch (IntrospectionException e) {
				throw new ConfigLoadException(e);
			} catch (NoSuchMethodException e) {
				throw new ConfigLoadException(e);
			} catch (Exception e) {
				throw new ConfigLoadException(e);
			}
		} catch (JAXBException e) {
			throw new ConfigLoadException(e);
		}

	}

	private void loadAnalyzers(AnalyzersType analyzers)
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException {
		String defaultAnalyzerName = analyzers.getDefault();
		defaultAnalyzerNameInjector.doInject(defaultAnalyzerName);
		for (AnalyzerType analyzer : analyzers.getAnalyzer()) {
			String name = analyzer.getName();
			ClassType clazz = analyzer.getAnalyzerClass();
			if (clazz != null) {
				String clazzName = clazz.getValue();

				Class<?> analyzerClass = Class.forName(clazzName);

				Class<? extends org.apache.lucene.analysis.Analyzer> concretAnalyzerClass = analyzerClass
						.asSubclass(org.apache.lucene.analysis.Analyzer.class);

				org.apache.lucene.analysis.Analyzer analyzerInstance = concretAnalyzerClass
						.newInstance();
				analyzerInjector
						.doInject(new Pair<String, org.apache.lucene.analysis.Analyzer>(
								name, analyzerInstance));
			}
		}
	}

	private void loadDirectory(DirectoryType directoryConfig)
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException, ConfigLoadException, IOException {

		Directory directory = null;

		RAMDirectoryType ramDirectory = directoryConfig.getRamDirectory();
		if (ramDirectory != null) {
			directory = new RAMDirectory();
		} else {
			SimpleFSDirectoryType sfsd = directoryConfig.getSimpleFSDirectory();
			if (sfsd != null) {
				String path = sfsd.getPath();
				directory = new SimpleFSDirectory(new File(path));
			} else {
				throw new ConfigLoadException("no available Directory");
			}
		}

		LockFactoryType lockFactoryConfig = directoryConfig.getLockFactory();
		if (lockFactoryConfig != null) {
			Class<?> lockFactoryClass = Class.forName(lockFactoryConfig
					.getClazz());
			Class<? extends LockFactory> concretClass = lockFactoryClass
					.asSubclass(LockFactory.class);
			LockFactory lockFactory = concretClass.newInstance();

			if (directory != null) {
				directory.setLockFactory(lockFactory);
			} else {
				throw new ConfigLoadException("no available Directory");
			}
		}

		directroyInjector.doInject(directory);
	}

	private void loadMappingConfig(MappingType mapping)
			throws SecurityException, IntrospectionException,
			NoSuchMethodException, Exception {
		XmlSchemaConfigParser xmlSchemaParser = new XmlSchemaConfigParser();

		for (WriteBeanType documentConfig : mapping.getWriteBean()) {
			DocumentConfigInfo documentConfigInfo = xmlSchemaParser
					.parse(documentConfig);
			log.debug(ConfigInfoPrinter.print(documentConfigInfo));
			documentConfigInfoInjector
					.doInject(new Pair<Class<?>, DocumentConfigInfo>(
							documentConfigInfo.documentedClass(),
							documentConfigInfo));
		}

		for (SearchBeanType undocumentConfig : mapping.getSearchBean()) {
			UndocumentConfigInfo undocumentConfigInfo = xmlSchemaParser
					.parse(undocumentConfig);
			log.debug(ConfigInfoPrinter.print(undocumentConfigInfo));
			undocumentConfigInfoInjector
					.doInject(new Pair<Class<?>, UndocumentConfigInfo>(
							undocumentConfigInfo.undocumentedClass(),
							undocumentConfigInfo));
		}

		DocumentAnnotationParser documentAnnotationParser = new DocumentAnnotationParser();
		UndocumentAnnotationParser undocumentAnnotationPraser = new UndocumentAnnotationParser();
		for (ClassType classType : mapping.getAnnotationedClass()) {
			Class<?> annotationedClass = Class.forName(classType.getValue());
			if (AnnotationParser.isDocumentAnnotated(annotationedClass)) {
				DocumentAnnotationWrapper documentAnnotationWrapper = documentAnnotationParser
						.parse(annotationedClass);
				DocumentConfigInfo documentConfigInfo = new DocumentAnnotationConfigAdapter(
						documentAnnotationWrapper);
				log.debug(ConfigInfoPrinter.print(documentConfigInfo));
				documentConfigInfoInjector
						.doInject(new Pair<Class<?>, DocumentConfigInfo>(
								documentConfigInfo.documentedClass(),
								documentConfigInfo));
			}
			if (AnnotationParser.isUndocumentAnnotated(annotationedClass)) {
				UndocumentAnnotationWrapper undocumentAnnotationWrapper = undocumentAnnotationPraser
						.parse(annotationedClass);
				UndocumentConfigInfo undocumentConfigInfo = new UndocumentAnnotationConfigAdapter(
						undocumentAnnotationWrapper);
				log.debug(ConfigInfoPrinter.print(undocumentConfigInfo));
				undocumentConfigInfoInjector
						.doInject(new Pair<Class<?>, UndocumentConfigInfo>(
								undocumentConfigInfo.undocumentedClass(),
								undocumentConfigInfo));
			}
		}

	}
}
